﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestDriven.Framework;
using System.Reflection;
using ClearUnit.TestRunners;
using ClearUnit.TestRunners.Results;
using Common.Net.DataStructures;
using System.Windows.Forms;
using Common.Net.Utilities;

namespace ClearUnit.Integration.TD.Net
{
    public class TdNetRunner : ITestRunner
    {
        public TestRunState RunAssembly(ITestListener testListener, Assembly assembly)
        {
            var result = new AssemblyRunner(assembly, i => RegisterTestInfoEvent(testListener, i)).Run();

            if (result.RunCount == 0)
                return TestRunState.NoTests;

            if (result.FailedCount > 0)
                return TestRunState.Failure;

            return TestRunState.Success;
        }

        private void RegisterTestInfoEvent(ITestListener listener, TestRunInfo testRunInfo)
        {
            if (testRunInfo is MethodRunResult)
                RegisterMethodRunResult(listener, (MethodRunResult)testRunInfo);
        }

        private static void RegisterMethodRunResult(ITestListener listener, MethodRunResult methodResult)
        {
            var testResult = new TestResult();

            testResult.FixtureType = methodResult.Fixture;
            testResult.Method = methodResult.Method;
            testResult.Name = methodResult.Method.Name;
            testResult.State = GetTestState(methodResult);
            testResult.TotalTests = 1;

            if (methodResult is MethodRunResult.Run)
            {
                var run = (MethodRunResult.Run)methodResult;
                testResult.TimeSpan = run.Length;
            }

            if (methodResult is MethodRunResult.Run.Failed)
            {
                var failed = (MethodRunResult.Run.Failed)methodResult;
                testResult.Message = failed.Exception.Message;
                testResult.StackTrace = failed.Exception.ToString();
            }

            listener.TestFinished(testResult);
        }

        static TestState GetTestState(MethodRunResult methodResult)
        {
            var mapper = new TypeValueMapper<TestState>();

            mapper.AddMapping<MethodRunResult.Run.Failed>(TestState.Failed);
            mapper.AddMapping<MethodRunResult.Run.Passed>(TestState.Passed);
            mapper.AddMapping<MethodRunResult.Skipped>(TestState.Ignored);

            return mapper.GetValue(methodResult);
        }

        static TestRunState GetTestRunState(MethodRunResult methodResult)
        {
            var mapper = new TypeValueMapper<TestRunState>();

            mapper.AddMapping<MethodRunResult.Run.Failed>(TestRunState.Failure);
            mapper.AddMapping<MethodRunResult.Run.Passed>(TestRunState.Success);

            return mapper.GetValue(methodResult);
        }

        static TestRunState GetTestRunState(TypeRunResult typeResult)
        {
            if (typeResult is TypeRunResult.Skipped)
                return TestRunState.NoTests;
            else
            {
                var typeRunResult = (TypeRunResult.Run)typeResult;

                if (typeRunResult.MethodsRun.Count() == 0)
                    return TestRunState.NoTests;

                if (typeRunResult.FailedCount > 0)
                    return TestRunState.Failure;

                return TestRunState.Success;
            }
        }

        public TestRunState RunMember(ITestListener testListener, Assembly assembly, MemberInfo member)
        {
            Action<TestRunInfo> listener = i => RegisterTestInfoEvent(testListener, i);

            if (member is MethodInfo)
            {
                var result = new MethodRunner((MethodInfo)member, true, listener).Run();

                return GetTestRunState(result);
            }
            else if (member is Type)
            {
                var result = new TypeRunner((Type)member, true, listener).Run();

                return GetTestRunState(result);
            }
            else
                throw ExceptionFactory.NotSupported("Testing {0} is not supported", member.GetType());
        }

        public TestRunState RunNamespace(ITestListener testListener, Assembly assembly, string ns)
        {
            throw new NotImplementedException();
        }
    }
}
